package org.gap.jseed;

import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.util.ArrayList;

import org.gap.jseed.injection.annotation.Environment;
import org.gap.jseed.utils.JSeedDefect;

/**
 * Performs a form of dependency injection by identifying the 
 * single public constructor defined in the service class given.  It then
 * uses the store to load the calss upon the {@code createInstance} call.
 */
public class DependencyInjectedBuilder<E> implements Builder<E> {

	private final Class<? extends E> service;
	private final ServiceStoreContainer store;
	private Constructor<?> constructor;

	public DependencyInjectedBuilder(Class<? extends E> service, ServiceStoreContainer store) {
		this.service = service;
		this.store = store;

		Constructor<?>[] constructors = service.getDeclaredConstructors();
		if (constructors.length > 1) {
			String message = String.format("%s must define only 1 constructor", service.getName());
			throw new IllegalStateException(message);
		}
				
		constructor = constructors[0];
		for (Class<?> each : constructor.getParameterTypes()) {
			if (!store.hasService(each) && !isBindable(each)) {
				String message = String.format("%s requires %s to be bound in the ServiceStore.  If the class is an inner class, then it must be made nested (static)", service.getName(), each.getName());
				throw new IllegalStateException(message);
			}
		}
	}

	@SuppressWarnings("unchecked")
	private boolean isBindable(Class<?> clazz) {
		for (Annotation each : clazz.getAnnotations()) {
			if (each.annotationType().equals(Environment.class)) {
				store.bind((Class<Object>)clazz, clazz);
				return true;
			}
		}
		return false;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public Class<? extends E> getType() {
		return service;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public E createInstance() {
		ArrayList<Object> arguments = new ArrayList<Object>();
		for (Class<?> each : constructor.getParameterTypes()) {
			arguments.add(store.get(each));
		}
		try {
			constructor.setAccessible(true);
			@SuppressWarnings("unchecked") E result = (E)constructor.newInstance(arguments.toArray());
			new MethodServiceInjector().inject(store, result);
			return result;
		} catch (Exception e) {
			e.printStackTrace();
			throw new JSeedDefect("Unexpected error occured. creating instance for [" + service.getName() + "]:\n\t" + e.getMessage() + ": " + e);
		}
	}
}
